Trò chơi đua xe động vật trong UNITY Engine
114.847 lượt xem;
- ScrollSnap.cs
- AnimalRacing /
- Scripts /
- Assets /
- project /
1 using UnityEngine;
2 using UnityEngine.EventSystems;
3 using UnityEngine.UI;
4 using System;
5 using UnityEngine.Events;
6
7 [DisallowMultipleComponent]
8 [RequireComponent(typeof(ScrollRect))]
9 public class ScrollSnap : UIBehaviour, IDragHandler, IEndDragHandler {
10 [SerializeField] public int startingIndex = 0;
11 [SerializeField] public bool wrapAround = false;
12 [SerializeField] public float lerpTimeMilliSeconds = 200f;
13 [SerializeField] public float triggerPercent = 5f;
14 [Range(0f, 10f)] public float triggerAcceleration = 1f;
15
16 public class OnLerpCompleteEvent : UnityEvent {}
17 public OnLerpCompleteEvent onLerpComplete;
18 public class OnReleaseEvent : UnityEvent<int> {}
19 public OnReleaseEvent onRelease;
20
21 int actualIndex;
22 int cellIndex;
23 ScrollRect scrollRect;
24 CanvasGroup canvasGroup;
25 RectTransform content;
26 Vector2 cellSize;
27 bool indexChangeTriggered = false;
28 bool isLerping = false;
29 DateTime lerpStartedAt;
30 Vector2 releasedPosition;
31 Vector2 targetPosition;
32
33 protected override void Awake() {
34 base.Awake();
35 actualIndex = startingIndex;
36 cellIndex = startingIndex;
37 this.onLerpComplete = new OnLerpCompleteEvent();
38 this.onRelease = new OnReleaseEvent();
39 this.scrollRect = GetComponent<ScrollRect>();
40 this.canvasGroup = GetComponent<CanvasGroup>();
41 this.content = scrollRect.content;
42 this.cellSize = content.GetComponent<GridLayoutGroup>().cellSize;
43 content.anchoredPosition = new Vector2(-cellSize.x * cellIndex, content.anchoredPosition.y);
44 int count = LayoutElementCount();
45 SetContentSize(count);
46
47 if(startingIndex < count) {
48 MoveToIndex(startingIndex);
49 }
50 }
51
52 void LateUpdate() {
53 if(isLerping) {
54 LerpToElement();
55 if(ShouldStopLerping()) {
56 isLerping = false;
57 canvasGroup.blocksRaycasts = true;
58 onLerpComplete.Invoke();
59 onLerpComplete.RemoveListener(WrapElementAround);
60 }
61 }
62 }
63
64 public void PushLayoutElement(LayoutElement element) {
65 element.transform.SetParent(content.transform, false);
66 SetContentSize(LayoutElementCount());
67 }
68
69 public void PopLayoutElement() {
70 LayoutElement[] elements = content.GetComponentsInChildren<LayoutElement>();
71 Destroy(elements[elements.Length - 1].gameObject);
72 SetContentSize(LayoutElementCount() - 1);
73 if(cellIndex == CalculateMaxIndex()) {
74 cellIndex -= 1;
75 }
76 }
77
78 public void UnshiftLayoutElement(LayoutElement element) {
79 cellIndex += 1;
80 element.transform.SetParent(content.transform, false);
81 element.transform.SetAsFirstSibling();
82 SetContentSize(LayoutElementCount());
83 content.anchoredPosition = new Vector2(content.anchoredPosition.x - cellSize.x, content.anchoredPosition.y);
84 }
85
86 public void ShiftLayoutElement() {
87 Destroy(GetComponentInChildren<LayoutElement>().gameObject);
88 SetContentSize(LayoutElementCount() - 1);
89 cellIndex -= 1;
90 content.anchoredPosition = new Vector2(content.anchoredPosition.x + cellSize.x, content.anchoredPosition.y);
91 }
92
93 public int LayoutElementCount() {
94 int count = 0;
95 foreach(Transform t in content.transform) {
96 if(t.GetComponent<LayoutElement>() != null) {
97 count += 1;
98 }
99 }
100 return count;
101 }
102
103 public int CurrentIndex {
104 get {
105 int count = LayoutElementCount();
106 int mod = actualIndex % count;
107 return mod >= 0 ? mod : count + mod;
108 }
109 }
110
111 public void OnDrag(PointerEventData data) {
112 float dx = data.delta.x;
113 float dt = Time.deltaTime * 1000f;
114 float acceleration = Mathf.Abs(dx / dt);
115 if(acceleration > triggerAcceleration && acceleration != Mathf.Infinity) {
116 indexChangeTriggered = true;
117 }
118 }
119
120 public void OnEndDrag(PointerEventData data) {
121 if(IndexShouldChangeFromDrag(data)) {
122 int direction = (data.pressPosition.x - data.position.x) > 0f ? 1 : -1;
123 SnapToIndex(cellIndex + direction);
124 } else {
125 StartLerping();
126 }
127 }
128
129 public void SnapToNext() {
130 SnapToIndex(cellIndex + 1);
131 }
132
133 public void SnapToPrev() {
134 SnapToIndex(cellIndex - 1);
135 }
136
137 public void SnapToIndex(int newCellIndex) {
138 int maxIndex = CalculateMaxIndex();
139 if(wrapAround && maxIndex > 0) {
140 actualIndex += newCellIndex - cellIndex;
141 cellIndex = newCellIndex;
142 onLerpComplete.AddListener(WrapElementAround);
143 } else {
144 // when it's the same it means it tried to go out of bounds
145 if(newCellIndex >= 0 && newCellIndex <= maxIndex) {
146 actualIndex += newCellIndex - cellIndex;
147 cellIndex = newCellIndex;
148 }
149 }
150 onRelease.Invoke(cellIndex);
151 StartLerping();
152 }
153
154 public void MoveToIndex(int newCellIndex) {
155 int maxIndex = CalculateMaxIndex();
156 if(newCellIndex >= 0 && newCellIndex <= maxIndex) {
157 actualIndex += newCellIndex - cellIndex;
158 cellIndex = newCellIndex;
159 }
160 onRelease.Invoke(cellIndex);
161 content.anchoredPosition = CalculateTargetPoisition(cellIndex);
162 }
163
164 void StartLerping() {
165 releasedPosition = content.anchoredPosition;
166 targetPosition = CalculateTargetPoisition(cellIndex);
167 lerpStartedAt = DateTime.Now;
168 canvasGroup.blocksRaycasts = false;
169 isLerping = true;
170 }
171
172 int CalculateMaxIndex() {
173 int cellPerFrame = Mathf.FloorToInt(scrollRect.GetComponent<RectTransform>().sizeDelta.x / cellSize.x);
174 return LayoutElementCount() - cellPerFrame;
175 }
176
177 bool IndexShouldChangeFromDrag(PointerEventData data) {
178 // acceleration was above threshold
179 if(indexChangeTriggered) {
180 indexChangeTriggered = false;
181 return true;
182 }
183 // dragged beyond trigger threshold
184 var offset = scrollRect.content.anchoredPosition.x + cellIndex * cellSize.x;
185 var normalizedOffset = Mathf.Abs(offset / cellSize.x);
186 return normalizedOffset * 100f > triggerPercent;
187 }
188
189 void LerpToElement() {
190 float t = (float)((DateTime.Now - lerpStartedAt).TotalMilliseconds / lerpTimeMilliSeconds);
191 float newX = Mathf.Lerp(releasedPosition.x, targetPosition.x, t);
192 content.anchoredPosition = new Vector2(newX, content.anchoredPosition.y);
193 }
194
195 void WrapElementAround() {
196 if(cellIndex <= 0) {
197 var elements = content.GetComponentsInChildren<LayoutElement>();
198 elements[elements.Length - 1].transform.SetAsFirstSibling();
199 cellIndex += 1;
200 content.anchoredPosition = new Vector2(content.anchoredPosition.x - cellSize.x, content.anchoredPosition.y);
201 } else if(cellIndex >= CalculateMaxIndex()) {
202 var element = content.GetComponentInChildren<LayoutElement>();
203 element.transform.SetAsLastSibling();
204 cellIndex -= 1;
205 content.anchoredPosition = new Vector2(content.anchoredPosition.x + cellSize.x, content.anchoredPosition.y);
206 }
207 }
208
209 void SetContentSize(int elementCount) {
210 content.sizeDelta = new Vector2(cellSize.x * elementCount, content.rect.height);
211 }
212
213 Vector2 CalculateTargetPoisition(int index) {
214 return new Vector2(-cellSize.x * index, content.anchoredPosition.y);
215 }
216
217 bool ShouldStopLerping() {
218 return Mathf.Abs(content.anchoredPosition.x - targetPosition.x) < 0.001;
219 }
220 }
2 using UnityEngine.EventSystems;
3 using UnityEngine.UI;
4 using System;
5 using UnityEngine.Events;
6
7 [DisallowMultipleComponent]
8 [RequireComponent(typeof(ScrollRect))]
9 public class ScrollSnap : UIBehaviour, IDragHandler, IEndDragHandler {
10 [SerializeField] public int startingIndex = 0;
11 [SerializeField] public bool wrapAround = false;
12 [SerializeField] public float lerpTimeMilliSeconds = 200f;
13 [SerializeField] public float triggerPercent = 5f;
14 [Range(0f, 10f)] public float triggerAcceleration = 1f;
15
16 public class OnLerpCompleteEvent : UnityEvent {}
17 public OnLerpCompleteEvent onLerpComplete;
18 public class OnReleaseEvent : UnityEvent<int> {}
19 public OnReleaseEvent onRelease;
20
21 int actualIndex;
22 int cellIndex;
23 ScrollRect scrollRect;
24 CanvasGroup canvasGroup;
25 RectTransform content;
26 Vector2 cellSize;
27 bool indexChangeTriggered = false;
28 bool isLerping = false;
29 DateTime lerpStartedAt;
30 Vector2 releasedPosition;
31 Vector2 targetPosition;
32
33 protected override void Awake() {
34 base.Awake();
35 actualIndex = startingIndex;
36 cellIndex = startingIndex;
37 this.onLerpComplete = new OnLerpCompleteEvent();
38 this.onRelease = new OnReleaseEvent();
39 this.scrollRect = GetComponent<ScrollRect>();
40 this.canvasGroup = GetComponent<CanvasGroup>();
41 this.content = scrollRect.content;
42 this.cellSize = content.GetComponent<GridLayoutGroup>().cellSize;
43 content.anchoredPosition = new Vector2(-cellSize.x * cellIndex, content.anchoredPosition.y);
44 int count = LayoutElementCount();
45 SetContentSize(count);
46
47 if(startingIndex < count) {
48 MoveToIndex(startingIndex);
49 }
50 }
51
52 void LateUpdate() {
53 if(isLerping) {
54 LerpToElement();
55 if(ShouldStopLerping()) {
56 isLerping = false;
57 canvasGroup.blocksRaycasts = true;
58 onLerpComplete.Invoke();
59 onLerpComplete.RemoveListener(WrapElementAround);
60 }
61 }
62 }
63
64 public void PushLayoutElement(LayoutElement element) {
65 element.transform.SetParent(content.transform, false);
66 SetContentSize(LayoutElementCount());
67 }
68
69 public void PopLayoutElement() {
70 LayoutElement[] elements = content.GetComponentsInChildren<LayoutElement>();
71 Destroy(elements[elements.Length - 1].gameObject);
72 SetContentSize(LayoutElementCount() - 1);
73 if(cellIndex == CalculateMaxIndex()) {
74 cellIndex -= 1;
75 }
76 }
77
78 public void UnshiftLayoutElement(LayoutElement element) {
79 cellIndex += 1;
80 element.transform.SetParent(content.transform, false);
81 element.transform.SetAsFirstSibling();
82 SetContentSize(LayoutElementCount());
83 content.anchoredPosition = new Vector2(content.anchoredPosition.x - cellSize.x, content.anchoredPosition.y);
84 }
85
86 public void ShiftLayoutElement() {
87 Destroy(GetComponentInChildren<LayoutElement>().gameObject);
88 SetContentSize(LayoutElementCount() - 1);
89 cellIndex -= 1;
90 content.anchoredPosition = new Vector2(content.anchoredPosition.x + cellSize.x, content.anchoredPosition.y);
91 }
92
93 public int LayoutElementCount() {
94 int count = 0;
95 foreach(Transform t in content.transform) {
96 if(t.GetComponent<LayoutElement>() != null) {
97 count += 1;
98 }
99 }
100 return count;
101 }
102
103 public int CurrentIndex {
104 get {
105 int count = LayoutElementCount();
106 int mod = actualIndex % count;
107 return mod >= 0 ? mod : count + mod;
108 }
109 }
110
111 public void OnDrag(PointerEventData data) {
112 float dx = data.delta.x;
113 float dt = Time.deltaTime * 1000f;
114 float acceleration = Mathf.Abs(dx / dt);
115 if(acceleration > triggerAcceleration && acceleration != Mathf.Infinity) {
116 indexChangeTriggered = true;
117 }
118 }
119
120 public void OnEndDrag(PointerEventData data) {
121 if(IndexShouldChangeFromDrag(data)) {
122 int direction = (data.pressPosition.x - data.position.x) > 0f ? 1 : -1;
123 SnapToIndex(cellIndex + direction);
124 } else {
125 StartLerping();
126 }
127 }
128
129 public void SnapToNext() {
130 SnapToIndex(cellIndex + 1);
131 }
132
133 public void SnapToPrev() {
134 SnapToIndex(cellIndex - 1);
135 }
136
137 public void SnapToIndex(int newCellIndex) {
138 int maxIndex = CalculateMaxIndex();
139 if(wrapAround && maxIndex > 0) {
140 actualIndex += newCellIndex - cellIndex;
141 cellIndex = newCellIndex;
142 onLerpComplete.AddListener(WrapElementAround);
143 } else {
144 // when it's the same it means it tried to go out of bounds
145 if(newCellIndex >= 0 && newCellIndex <= maxIndex) {
146 actualIndex += newCellIndex - cellIndex;
147 cellIndex = newCellIndex;
148 }
149 }
150 onRelease.Invoke(cellIndex);
151 StartLerping();
152 }
153
154 public void MoveToIndex(int newCellIndex) {
155 int maxIndex = CalculateMaxIndex();
156 if(newCellIndex >= 0 && newCellIndex <= maxIndex) {
157 actualIndex += newCellIndex - cellIndex;
158 cellIndex = newCellIndex;
159 }
160 onRelease.Invoke(cellIndex);
161 content.anchoredPosition = CalculateTargetPoisition(cellIndex);
162 }
163
164 void StartLerping() {
165 releasedPosition = content.anchoredPosition;
166 targetPosition = CalculateTargetPoisition(cellIndex);
167 lerpStartedAt = DateTime.Now;
168 canvasGroup.blocksRaycasts = false;
169 isLerping = true;
170 }
171
172 int CalculateMaxIndex() {
173 int cellPerFrame = Mathf.FloorToInt(scrollRect.GetComponent<RectTransform>().sizeDelta.x / cellSize.x);
174 return LayoutElementCount() - cellPerFrame;
175 }
176
177 bool IndexShouldChangeFromDrag(PointerEventData data) {
178 // acceleration was above threshold
179 if(indexChangeTriggered) {
180 indexChangeTriggered = false;
181 return true;
182 }
183 // dragged beyond trigger threshold
184 var offset = scrollRect.content.anchoredPosition.x + cellIndex * cellSize.x;
185 var normalizedOffset = Mathf.Abs(offset / cellSize.x);
186 return normalizedOffset * 100f > triggerPercent;
187 }
188
189 void LerpToElement() {
190 float t = (float)((DateTime.Now - lerpStartedAt).TotalMilliseconds / lerpTimeMilliSeconds);
191 float newX = Mathf.Lerp(releasedPosition.x, targetPosition.x, t);
192 content.anchoredPosition = new Vector2(newX, content.anchoredPosition.y);
193 }
194
195 void WrapElementAround() {
196 if(cellIndex <= 0) {
197 var elements = content.GetComponentsInChildren<LayoutElement>();
198 elements[elements.Length - 1].transform.SetAsFirstSibling();
199 cellIndex += 1;
200 content.anchoredPosition = new Vector2(content.anchoredPosition.x - cellSize.x, content.anchoredPosition.y);
201 } else if(cellIndex >= CalculateMaxIndex()) {
202 var element = content.GetComponentInChildren<LayoutElement>();
203 element.transform.SetAsLastSibling();
204 cellIndex -= 1;
205 content.anchoredPosition = new Vector2(content.anchoredPosition.x + cellSize.x, content.anchoredPosition.y);
206 }
207 }
208
209 void SetContentSize(int elementCount) {
210 content.sizeDelta = new Vector2(cellSize.x * elementCount, content.rect.height);
211 }
212
213 Vector2 CalculateTargetPoisition(int index) {
214 return new Vector2(-cellSize.x * index, content.anchoredPosition.y);
215 }
216
217 bool ShouldStopLerping() {
218 return Mathf.Abs(content.anchoredPosition.x - targetPosition.x) < 0.001;
219 }
220 }
when it's the same it means it tried to go out of bounds
acceleration was above threshold
dragged beyond trigger threshold